package com.example.monit.service;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.BulkResponse;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.bulk.BulkOperation;
import co.elastic.clients.elasticsearch.sql.QueryResponse;
import co.elastic.clients.json.JsonData;
import com.example.monit.bean.JavaScriptError;
import com.example.monit.bean.ResponseRust;
import com.example.monit.dto.QueryJsErrDto;
import com.example.monit.dto.TotalDto;
import com.example.monit.enums.IndexName;
import com.example.monit.utils.CityUtils;
import com.example.monit.utils.TimeUtils;
import com.example.monit.vo.JavaScriptErrorTotalVo;
import com.example.monit.vo.JavaScriptErrorVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class JavascriptErrorServiceImpl extends BasEsService implements JavascriptErrorService {
    @Autowired
    ElasticsearchClient client;

    @Override
    public ResponseRust uploadError(List<JavaScriptError> javaScriptError) throws IOException {
//        for (JavaScriptError error : javaScriptError) {
//            error.setCity(CityUtils.getCity());
//        }
//        List<BulkOperation> operationList = javaScriptError.stream().map(e ->
//                        BulkOperation.of(builder ->
//                                builder.index(builder1 -> builder1.document(e).index(IndexName.JavaScriptError))))
//                .collect(Collectors.toList());
//        BulkResponse response = client.bulk(builder -> builder.operations(operationList));
//        if (response.errors()) {
//            log.error("上报数据异常错误");
//        }
        return ResponseRust.success_creat();
    }

    @Override
    public ResponseRust totalError(JavaScriptErrorTotalVo javaScriptErrorTotalVo) throws IOException {
        SearchRequest searchRequest = SearchRequest.of(s ->
                s.index(IndexName.JavaScriptError)
                        .query(getQuery(javaScriptErrorTotalVo))
                        .aggregations("count", getAggregation(javaScriptErrorTotalVo))
                        .size(0));

        SearchResponse<JavaScriptError> response = client.search(searchRequest, JavaScriptError.class);
        Map<String, Aggregate> aggregations = response.aggregations();
        Aggregate count = aggregations.get("count");

        // 5个范围
        Buckets<HistogramBucket> buckets = count.histogram().buckets();
        List<TotalDto> data = totalData(buckets, javaScriptErrorTotalVo);
        return ResponseRust.success(data);
    }

    private Aggregation getAggregation(JavaScriptErrorTotalVo javaScriptErrorTotalVo) {
        HashMap<String, Aggregation> aggregationHashMap = new HashMap<>(2);
        aggregationHashMap.put("userCount", Aggregation.of(userCount -> userCount.cardinality(e -> e.field("userID"))));
        aggregationHashMap.put("pageCount", Aggregation.of(pageCount -> pageCount.cardinality(e -> e.field("pageUrl"))));
        if (!StringUtils.hasLength(javaScriptErrorTotalVo.getGranularity())) {
            javaScriptErrorTotalVo.setGranularity("1d");
        }
        int intValue = Integer.parseInt(javaScriptErrorTotalVo.getGranularity().split("[smhdMy]")[0]);
        String type = javaScriptErrorTotalVo.getGranularity().split(intValue + "")[1];
        ChronoUnit unit = TimeUtils.getType(type);

        return Aggregation.of(count -> count.histogram(histogram ->
                        histogram.field("errorTime").interval((double) unit.getDuration().toMillis() * intValue))
                .aggregations(aggregationHashMap));
    }

    private Query getQuery(JavaScriptErrorTotalVo javaScriptErrorTotalVo) {
        List<Query> queries = new LinkedList<>();
        if (StringUtils.hasLength(javaScriptErrorTotalVo.getUrl())) {
            queries.add(TermQuery.of(t -> t.field("path").value(e -> e.stringValue(javaScriptErrorTotalVo.getUrl())))._toQuery());
        }
        if (StringUtils.hasLength(javaScriptErrorTotalVo.getMsg())) {
            queries.add(TermQuery.of(t -> t.field("msg").value(e -> e.stringValue(javaScriptErrorTotalVo.getMsg())))._toQuery());
        }

        queries.add(TermQuery.of(t -> t.field("appId").value(e -> e.stringValue(javaScriptErrorTotalVo.getApp_id())))._toQuery());
        queries.add(TermQuery.of(t -> t.field("mainType").value(e -> e.longValue(javaScriptErrorTotalVo.getMain_type())))._toQuery());
        queries.add(TermQuery.of(t -> t.field("subType").value(e -> e.longValue(javaScriptErrorTotalVo.getSub_type())))._toQuery());
        queries.add(RangeQuery.of(r ->
                r.gte(JsonData.of(javaScriptErrorTotalVo.getStart_time().toInstant(ZoneOffset.of("+0")).toEpochMilli()))
                        .lte(JsonData.of(javaScriptErrorTotalVo.getEnd_time().toInstant(ZoneOffset.of("+0")).toEpochMilli()))
                        .field("errorTime"))._toQuery());

        return Query.of(query -> query.bool(bool -> bool.must(queries)));
    }

    @Override
    public ResponseRust getErrorList(JavaScriptErrorVo javaScriptErrorVo) throws IOException {
        String sqlString = "SELECT msg,count(msg),url ,line,column,userID,pageUrl\n" +
                "            FROM \"javascript_error\"\n" +
                "            where appId='%s' and mainType=%d and subType=%d and errorTime between %d and %d\n" +
                "            group by msg,url, line,column,userID,pageUrl\n" +
                "            order by count(msg) desc";

        String format = String.format(sqlString,
                javaScriptErrorVo.getApp_id(),
                javaScriptErrorVo.getMain_type(),
                javaScriptErrorVo.getSub_type(),
                javaScriptErrorVo.getStart_time().toInstant(ZoneOffset.of("+0")).toEpochMilli(),
                javaScriptErrorVo.getEnd_time().toInstant(ZoneOffset.of("+0")).toEpochMilli());

        QueryResponse query = client.sql().query(e -> e.query(format));

        List<List<JsonData>> list = query.rows();

        HashMap<String, QueryJsErrDto> map = new HashMap<>();
        for (List<JsonData> jsonData : list) {
            String msg = jsonData.get(0).to(String.class);
            long count = jsonData.get(1).to(Long.class);
            String url = jsonData.get(2).to(String.class);
            String line = jsonData.get(3).to(String.class);
            String column = jsonData.get(4).to(String.class);

            String userID = jsonData.get(5).to(String.class);
            String pageUrl = jsonData.get(6).to(String.class);

            QueryJsErrDto build = QueryJsErrDto.builder()
                    .msg(msg)
                    .count(count)
                    .url(url)
                    .line(line)
                    .column(column)
                    .userCount(1)
                    .pageCount(1)
                    .userList(Arrays.asList(userID))
                    .pageList(Arrays.asList(pageUrl))
                    .count(count).build();

            String key = msg + url + line + column;
            QueryJsErrDto queryDto = map.get(key);
            if (ObjectUtils.isEmpty(queryDto)) {
                map.put(pageUrl, build);
            } else {
                if (!queryDto.getUserList().contains(userID)) {
                    queryDto.setUserCount(queryDto.getUserCount() + 1);
                    queryDto.getUserList().add(userID);
                }
                if (!queryDto.getPageList().contains(pageUrl)) {
                    queryDto.setPageCount(queryDto.getPageCount() + 1);
                    queryDto.getPageList().add(pageUrl);
                }
                queryDto.setCount(queryDto.getCount() + build.getCount());
            }
        }

        Collection<QueryJsErrDto> values = map.values();

        if (!ObjectUtils.isEmpty(javaScriptErrorVo.getSize())) {
            values = values.stream().limit(javaScriptErrorVo.getSize()).collect(Collectors.toList());
        }

        return ResponseRust.success(values);
    }
}
